/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.replica.replicaisland;

import com.replica.replicaisland.CollisionParameters.HitType;
import com.replica.replicaisland.GameObject.ActionType;
import com.replica.replicaisland.GameObject.Team;
import com.replica.replicaisland.GameObjectFactory.GameObjectType;

/**
 * A general-purpose component that responds to dynamic collision notifications.
 * This component may be configured to produce common responses to hit (taking
 * damage, being knocked back, etc), or it can be derived for entirely different
 * responses. This component must exist on an object for that object to respond
 * to dynamic collisions.
 */
public class HitReactionComponent extends GameComponent {
	private static final float ATTACK_PAUSE_DELAY = (1.0f / 60) * 4;
	private final static float DEFAULT_BOUNCE_MAGNITUDE = 200.0f;
	private final static float EVENT_SEND_DELAY = 5.0f;

	private boolean mPauseOnAttack;
	private float mPauseOnAttackTime;
	private boolean mBounceOnHit;
	private float mBounceMagnitude;
	private float mInvincibleAfterHitTime;
	private float mLastHitTime;
	private boolean mInvincible;
	private boolean mDieOnCollect;
	private boolean mDieOnAttack;
	private ChangeComponentsComponent mPossessionComponent;
	private InventoryComponent.UpdateRecord mInventoryUpdate;
	private LauncherComponent mLauncherComponent;
	private int mLauncherHitType;
	private float mInvincibleTime;
	private int mGameEventHitType;
	private int mGameEventOnHit;
	private int mGameEventIndexData;
	private float mLastGameEventTime;
	private boolean mForceInvincibility;
	private SoundSystem.Sound mTakeHitSound;
	private SoundSystem.Sound mDealHitSound;
	private int mDealHitSoundHitType;
	private int mTakeHitSoundHitType;

	private GameObjectType mSpawnOnDealHitObjectType;
	private int mSpawnOnDealHitHitType;
	private boolean mAlignDealHitObjectToVictimX;
	private boolean mAlignDealHitObjectToVictimY;

	public HitReactionComponent() {
		super();
		reset();
		setPhase(ComponentPhases.PRE_DRAW.ordinal());
	}

	@Override
	public void reset() {
		mPauseOnAttack = false;
		mPauseOnAttackTime = ATTACK_PAUSE_DELAY;
		mBounceOnHit = false;
		mBounceMagnitude = DEFAULT_BOUNCE_MAGNITUDE;
		mInvincibleAfterHitTime = 0.0f;
		mInvincible = false;
		mDieOnCollect = false;
		mDieOnAttack = false;
		mPossessionComponent = null;
		mInventoryUpdate = null;
		mLauncherComponent = null;
		mLauncherHitType = HitType.LAUNCH;
		mInvincibleTime = 0.0f;
		mGameEventOnHit = -1;
		mGameEventIndexData = 0;
		mLastGameEventTime = -1.0f;
		mGameEventHitType = HitType.INVALID;
		mForceInvincibility = false;
		mTakeHitSound = null;
		mDealHitSound = null;
		mSpawnOnDealHitObjectType = GameObjectType.INVALID;
		mSpawnOnDealHitHitType = HitType.INVALID;
		mDealHitSoundHitType = HitType.INVALID;
		mAlignDealHitObjectToVictimX = false;
		mAlignDealHitObjectToVictimY = false;
	}

	/** Called when this object attacks another object. */
	public void hitVictim(GameObject parent, GameObject victim, int hitType,
			boolean hitAccepted) {
		if (hitAccepted) {
			if (mPauseOnAttack && hitType == HitType.HIT) {
				TimeSystem time = sSystemRegistry.timeSystem;
				time.freeze(mPauseOnAttackTime);
			}

			if (mDieOnAttack) {
				parent.life = 0;
			}

			if (hitType == mLauncherHitType && mLauncherComponent != null) {
				mLauncherComponent.prepareToLaunch(victim, parent);
			}

			if (mDealHitSound != null
					&& (hitType == mDealHitSoundHitType || mDealHitSoundHitType == HitType.INVALID)) {
				SoundSystem sound = sSystemRegistry.soundSystem;
				if (sound != null) {
					sound.play(mDealHitSound, false,
							SoundSystem.PRIORITY_NORMAL);
				}
			}

			if (mSpawnOnDealHitObjectType != GameObjectType.INVALID
					&& hitType == mSpawnOnDealHitHitType) {
				final float x = mAlignDealHitObjectToVictimX ? victim
						.getPosition().x : parent.getPosition().x;
				final float y = mAlignDealHitObjectToVictimY ? victim
						.getPosition().y : parent.getPosition().y;

				GameObjectFactory factory = sSystemRegistry.gameObjectFactory;
				GameObjectManager manager = sSystemRegistry.gameObjectManager;

				if (factory != null) {
					GameObject object = factory.spawn(
							mSpawnOnDealHitObjectType, x, y,
							parent.facingDirection.x < 0.0f);

					if (object != null && manager != null) {
						manager.add(object);
					}
				}
			}
		}
	}

	/** Called when this object is hit by another object. */
	public boolean receivedHit(GameObject parent, GameObject attacker,
			int hitType) {
		final TimeSystem time = sSystemRegistry.timeSystem;
		final float gameTime = time.getGameTime();

		if (mGameEventHitType == hitType
				&& mGameEventHitType != HitType.INVALID) {
			if (mLastGameEventTime < 0.0f
					|| gameTime > mLastGameEventTime + EVENT_SEND_DELAY) {
				LevelSystem level = sSystemRegistry.levelSystem;
				level.sendGameEvent(mGameEventOnHit, mGameEventIndexData, true);
			} else {
				// special case. If we're waiting for a hit type to spawn an
				// event and
				// another event has just happened, eat this hit so we don't
				// miss
				// the chance to send the event.
				hitType = HitType.INVALID;
			}
			mLastGameEventTime = gameTime;
		}

		switch (hitType) {
		case HitType.INVALID:
			break;

		case HitType.HIT:
			// don't hit our friends, if we have friends.
			final boolean sameTeam = (parent.team == attacker.team && parent.team != Team.NONE);
			if (!mForceInvincibility && !mInvincible && parent.life > 0
					&& !sameTeam) {
				parent.life -= 1;

				if (mBounceOnHit && parent.life > 0) {
					VectorPool pool = sSystemRegistry.vectorPool;
					Vector2 newVelocity = pool.allocate(parent.getPosition());
					newVelocity.subtract(attacker.getPosition());
					newVelocity.set(0.5f * Utils.sign(newVelocity.x),
							0.5f * Utils.sign(newVelocity.y));
					newVelocity.multiply(mBounceMagnitude);
					parent.setVelocity(newVelocity);
					parent.getTargetVelocity().zero();
					pool.release(newVelocity);
				}

				if (mInvincibleAfterHitTime > 0.0f) {
					mInvincible = true;
					mInvincibleTime = mInvincibleAfterHitTime;
				}

			} else {
				// Ignore this hit.
				hitType = HitType.INVALID;
			}
			break;
		case HitType.DEATH:
			// respect teams?
			parent.life = 0;
			break;
		case HitType.COLLECT:
			if (mInventoryUpdate != null && parent.life > 0) {
				InventoryComponent attackerInventory = attacker
						.findByClass(InventoryComponent.class);
				if (attackerInventory != null) {
					attackerInventory.applyUpdate(mInventoryUpdate);
				}
			}
			if (mDieOnCollect && parent.life > 0) {
				parent.life = 0;
			}
			break;
		case HitType.POSSESS:
			if (mPossessionComponent != null && parent.life > 0
					&& attacker.life > 0) {
				mPossessionComponent.activate(parent);
			} else {
				hitType = HitType.INVALID;
			}
			break;
		case HitType.LAUNCH:
			break;

		default:
			break;
		}

		if (hitType != HitType.INVALID) {
			if (mTakeHitSound != null && hitType == mTakeHitSoundHitType) {
				SoundSystem sound = sSystemRegistry.soundSystem;
				if (sound != null) {
					sound.play(mTakeHitSound, false,
							SoundSystem.PRIORITY_NORMAL);
				}
			}
			mLastHitTime = gameTime;
			parent.setCurrentAction(ActionType.HIT_REACT);
			parent.lastReceivedHitType = hitType;

		}

		return hitType != HitType.INVALID;
	}

	@Override
	public void update(float timeDelta, BaseObject parent) {
		GameObject parentObject = (GameObject) parent;
		TimeSystem time = sSystemRegistry.timeSystem;

		final float gameTime = time.getGameTime();

		if (mInvincible && mInvincibleTime > 0) {
			if (time.getGameTime() > mLastHitTime + mInvincibleTime) {
				mInvincible = false;
			}
		}

		// This means that the lastReceivedHitType will persist for two frames,
		// giving all systems
		// a chance to react.
		if (gameTime - mLastHitTime > timeDelta) {
			parentObject.lastReceivedHitType = HitType.INVALID;
		}
	}

	public void setPauseOnAttack(boolean pause) {
		mPauseOnAttack = pause;
	}

	public void setPauseOnAttackTime(float seconds) {
		mPauseOnAttackTime = seconds;
	}

	public void setBounceOnHit(boolean bounce) {
		mBounceOnHit = bounce;
	}

	public void setBounceMagnitude(float magnitude) {
		mBounceMagnitude = magnitude;
	}

	public void setInvincibleTime(float time) {
		mInvincibleAfterHitTime = time;
	}

	public void setDieWhenCollected(boolean die) {
		mDieOnCollect = true;
	}

	public void setDieOnAttack(boolean die) {
		mDieOnAttack = die;
	}

	public void setInvincible(boolean invincible) {
		mInvincible = invincible;
	}

	public void setPossessionComponent(ChangeComponentsComponent component) {
		mPossessionComponent = component;
	}

	public void setInventoryUpdate(InventoryComponent.UpdateRecord update) {
		mInventoryUpdate = update;
	}

	public void setLauncherComponent(LauncherComponent component,
			int launchHitType) {
		mLauncherComponent = component;
		mLauncherHitType = launchHitType;
	}

	public void setSpawnGameEventOnHit(int hitType, int gameFlowEventType,
			int indexData) {
		mGameEventHitType = hitType;
		mGameEventOnHit = gameFlowEventType;
		mGameEventIndexData = indexData;
		if (hitType == HitType.INVALID) {
			// The game event has been cleared, so reset the timer blocking a
			// subsequent event.
			mLastGameEventTime = -1.0f;
		}
	}

	public final void setForceInvincible(boolean force) {
		mForceInvincibility = force;
	}

	public final void setTakeHitSound(int hitType, SoundSystem.Sound sound) {
		mTakeHitSoundHitType = hitType;
		mTakeHitSound = sound;
	}

	public final void setDealHitSound(int hitType, SoundSystem.Sound sound) {
		mDealHitSound = sound;
		mDealHitSoundHitType = hitType;
	}

	public final void setSpawnOnDealHit(int hitType, GameObjectType objectType,
			boolean alignToVictimX, boolean alignToVicitmY) {
		mSpawnOnDealHitObjectType = objectType;
		mSpawnOnDealHitHitType = hitType;
		mAlignDealHitObjectToVictimX = alignToVictimX;
		mAlignDealHitObjectToVictimY = alignToVicitmY;
	}

}
